/* Copyright 2010 John L. Reilly

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */

package com.riq;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.riq.entity.Alert;
import com.riq.entity.Department;
import com.riq.entity.Location;
import com.riq.entity.Member;
import com.riq.entity.Status;
import com.riq.entity.Tracking;
import com.riq.nonpersist.ReportDept;
import com.riq.nonpersist.ReportLocation;


//@SuppressWarnings("serial")
public class ReportingPrepServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;
  private static final Logger log = Logger.getLogger(Tracking.class.getName());
  private static final int MAX_COOKIE_AGE_MS = 60*60*24*1000;  // for cookie age
  private static final Long INIT_LONG = Long.valueOf("999999999"); 
  private Long deptId = INIT_LONG;
  private Long alertId = INIT_LONG;
  private String formTimeStamp = System.currentTimeMillis()+"|"+deptId+"|"+alertId;
  
  private List<Department> depts = new ArrayList<Department>();
  private List<Long> allDeptIds = new ArrayList<Long>();
  private List<Member> members = new ArrayList<Member>();
  
  private Map<Long, List<Location>> mDeptLocations = new HashMap<Long, List<Location>>();
  private Map<Long, List<String>> mDeptLocationTypes = new HashMap<Long, List<String>>();
  private Map<Long, List<Status>> mDeptLocationStatuses = new HashMap<Long, List<Status>>();
  private Map<Long, Long> mDeptLocationSelectedStatus = new HashMap<Long, Long>();
  
  private Map<Long, List<Member>> mDeptMembers = new HashMap<Long, List<Member>>();
  private Map<Long, List<String>> mDeptMemberTypes = new HashMap<Long, List<String>>();
  private Map<Long, List<Member>> mDeptLocationMembers = new HashMap<Long, List<Member>>();
  private Map<Long, Tracking> mMemberCurrentLocation = new HashMap<Long, Tracking>();
  private List<Member> unassignedMembers = new ArrayList<Member>();

//  private List<Member> dActivityMembers = new ArrayList<Member>();
//  private List<Location> dActivityLocations = new ArrayList<Location>();
//  private Map<Long, List<Member>> mDeptActivityMember = new HashMap<Long, List<Member>>();

  private List<Member> allMembers = new ArrayList<Member>();
  private List<Member> activeMembers = new ArrayList<Member>();
  private List<Member> inactiveMembers = new ArrayList<Member>();

  private Map<String, List<Member>> memberGroups = new HashMap<String, List<Member>>();
  private Map<Long, Map<String, List<Member>>> alertMemberGroups = 
    new HashMap<Long, Map<String, List<Member>>>();

  
  @Override
  public void doPost(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
    doGet(request, response);
  }
  

  @Override
  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
  
  String intent = request.getParameter("intent");
  String path = request.getParameter("path");
  System.out.println("path94: " + path);

  int tempPrimaryDeptFlag = 0;
  
  Cookie[] cookie = request.getCookies();
  deptId = Long.valueOf(ServletUtilities.getCookieValue(cookie, "cvDeptId", "99999"));
  System.out.println("deptId from cookie: " + deptId.toString());
  
  // get alert and calculate elapsed time
  alert(response, request);
  // using cookie, determine primary dept and get it plus its mutual aid depts
  List<Department> pamDepts = pamDepts(deptId); 
  // get the dept ids (primary dept and its mutual aid depts
  List<Long> pamIds = aDeptIds(pamDepts);
  // create the map for dept:locations 
  mDeptLocations(pamIds);
  // create the map for location: statuses
  mDeptLocationStatuses();
  // get the members for the pam depts
  mDeptMembers(pamIds);  
  // create the map for member: currerntLocation
  mMemberLocation(alertId);
  // create the map for location: members
  mDeptLocationMembers();
  // determine which members have been manually added and still unassigned to a location
  unassignedMembers();
  // create the various member groups
  memberGroups(mDeptLocationMembers);
  // create the map for location: statuses: selectedStatus
  mDeptLocationSelectedStatuses();
  // gather the location types for pamDepts (used in report grouping logic)
  locationTypes(mDeptLocations);
  // gather the member types for pamDepts (used in report grouping logic)
  memberTypes(mDeptMembers);
  
  List<ReportDept> reportDepts = new ArrayList<ReportDept>();
  for (Department d: pamDepts) {
    System.out.println("dept: " + d.getshortName());

    // create ReportLocation record for each dept Location
    List<ReportLocation> reportLocations = new ArrayList<ReportLocation>();
    if (mDeptLocations.get(d.getid()) != null) {
      for (Location l : mDeptLocations.get(d.getid())) {
        System.out.println("ReportLocation loop: " + l.getshortName());
  
        // set activeFlag for Location (any selected Status or any assigned Member)
        String tempActiveLocation = "yes";
        if (mDeptLocationMembers.get(l) == null && (mDeptLocationSelectedStatus.get(l) != null)) {
          tempActiveLocation = "no";
        }
  
        ReportLocation reportLocationEntry = new ReportLocation(       
          tempActiveLocation,
          l,
          mDeptLocationStatuses.get(l.getid()),
          mDeptLocationSelectedStatus.get(l.getid()),
          mDeptLocationMembers.get(l.getid())
          );
        reportLocations.add(reportLocationEntry);
        System.out.println("Added reportLocation for " + l.getshortName() + "|" + l.getid());
        System.out.println("ReportLocations qty:  " + reportLocations.size());
        System.out.println("ReportLocations mDeptLocMember qty:  " + mDeptLocationMembers.size());

      }
    }
    
    // the primary dept is always listed first in the collection
    String tempDeptType;
    if (tempPrimaryDeptFlag == 0) {
      tempDeptType = "primary";
    } else {
      tempDeptType = "mutual";
    }
    
    
    
    // create ReportDept record
    ReportDept reportDept = new ReportDept(
        tempDeptType,
        d,
        mDeptLocationTypes.get(d.getid()),
        reportLocations,
        mDeptMemberTypes.get(d.getid()),
        unassignedMembers,
        activeMembers, 
        inactiveMembers
        );
    reportDepts.add(reportDept);
    System.out.println("reportDept added for: " + d.getid());
    System.out.println("-----------------------------------------------");

    
    // increment for mutual aid depts (if any) - primary dept always first in collection
    tempPrimaryDeptFlag++;
    Collections.sort(reportDept.locations());
  }

      // alert attribute set in alert method
      request.setAttribute("path", path);
      request.setAttribute("intent", intent);
      request.setAttribute("reportDepts", reportDepts);
      request.setAttribute("formTimestamp", formTimeStamp);
      
      if ("addResources".equalsIgnoreCase(path)) {
        System.out.println("addResouces passed");
        RequestDispatcher dispatcher = 
          getServletContext().getRequestDispatcher("/parResourcesMulti.jsp?");
        dispatcher.forward(request, response); 
      } else {
        RequestDispatcher dispatcher = 
          getServletContext().getRequestDispatcher("/parCommandMulti.jsp?");
        dispatcher.forward(request, response); 
      }
    
  }

  
  /**
   * Find the department objects for the provided list of department ids.
   * Result is a privately available list of pamIds of type long.
   * @param list of pamIds
   * @return none
   */  
  private List<Department> pamDepts(Long deptId) {
    
    PersistenceManager pm = PMF.get().getPersistenceManager();
    depts = new ArrayList<Department>();
    // add primary dept
    Department dPrimary = pm.getObjectById(Department.class, deptId);
    depts.add(dPrimary);  
    // primary dept's mutual aid depts
    String dMutualAid = dPrimary.getmutualAid();
    if (dMutualAid.length() != 0) {
      
      if (dMutualAid.contains(",")) {
        String[] maDepts = dMutualAid.split(", ", -1);
        for (int i=0; i< maDepts.length; i++) {
          Long maDeptId = Long.valueOf(maDepts[i]);
          Department dMAs = pm.getObjectById(Department.class, maDeptId);
          if (dMAs != null) {
          depts.add(dMAs);
          }
        }
      } else {
        Department dMAsolo = pm.getObjectById(Department.class, Long.valueOf((dMutualAid).trim()));
        if (dMAsolo != null) {
        depts.add(dMAsolo);
        }
      }
    } 
    System.out.println("Depts Qty: " + depts.size());
    System.out.println("Exit: pamDepts");
    return depts;
  }

  
  /**
   * Find the department objects for the provided list of department ids.
   * Result is a privately available list of pamIds of type long.
   * @param list of pamIds
   * @return none
   */   
  private List<Long> aDeptIds(List<Department> pamDepts) {
    allDeptIds = new ArrayList<Long>();
    // add primary dept (always first in the collection)
    allDeptIds.add(pamDepts.get(0).getid()); 
    // add any and all mutual aid depts - note: i=1 (not zero) to pass over primary dept
    for (int i = 1; i < pamDepts.size(); i++) {
    allDeptIds.add(pamDepts.get(i).getid());
    } 
    System.out.println("Exit: aDeptIds");
    return allDeptIds;
  }
  
  
  /**
   * Create a map of the owned location objects for the provided list of department ids.
   * Result is a privately available map of dept:list<Location>.
   * @param list of pamIds
   * @return none
   */  
  private void mDeptLocations(List<Long> deptIds) {
    
    PersistenceManager pm = PMF.get().getPersistenceManager(); // TODO necessary? common? 
    mDeptLocations.clear();

    for (Long d : deptIds) {
      List<Location> tempLocations = new ArrayList<Location>();
      Query q = pm.newQuery(Location.class, "deptId == " + d );
      q.setOrdering("deptId asc, key asc");
      tempLocations.addAll((List<Location>) pm.newQuery(q).execute());
      System.out.println("deptLocations ending qty: " + tempLocations.size());
      mDeptLocations.put(d, tempLocations);
      System.out.println("mDeptLocations running qty: " + mDeptLocations.size());
    }
    System.out.println("mDeptLocations ending qty: " + mDeptLocations.size());
    System.out.println("Exit: mDeptLocations");
    
  }
  

  /**
   * Find the owned status objects for the provided list of location ids.
   * Result is a privately available map of dept:list<Location>.
   * @param list of pamIds
   * @return none
   */
  public void mDeptLocationStatuses() {
    System.out.println("Inside mDeptLocationStatuses");
    PersistenceManager pm = PMF.get().getPersistenceManager(); 
    mDeptLocationStatuses.clear();
    for (int d=0; d<mDeptLocations.size(); d++) {
      System.out.println("mDeptLocation qty: " + mDeptLocations.size());
//      dLocationStatuses.clear();
      if (mDeptLocations.get(d) != null) {
        for (int l=0; l<mDeptLocations.get(d).size(); l++) {
          List<Status> dLocationStatuses = new ArrayList<Status>();
          List<Long> keys = new ArrayList<Long>();
          keys.addAll(mDeptLocations.get(d).get(l).getstatusKeys());
          Query q = pm.newQuery(Status.class, ":p.contains(id)");
          dLocationStatuses.addAll((List<Status>) pm.newQuery(q).execute(keys)); 
          mDeptLocationStatuses.put(mDeptLocations.get(d).get(l).getid(), dLocationStatuses);
          System.out.println("dLocationStatuses in deptLocStat: " + dLocationStatuses.size());
        }
      }
    }
    System.out.println("mDeptLocationStatuses qty: " + mDeptLocationStatuses.size());
    System.out.println("Exit: mDeptLocationStatuses");
  }
  
  
  /**
   * Find the owned status objects for the provided list of location ids.
   * Result is a privately available map of dept:list<Location>.
   * Note: mDeptActivityStatus map must be created prior to calling this method.
   * @param list of pamIds
   * @return none
   */
  public void mDeptLocationSelectedStatuses() {
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Query q = pm.newQuery(Tracking.class, "alertId == " + alertId  + " && type == 'locstat' ");
    q.setOrdering("timeStamp desc");
    List<Tracking> tracks = (List<Tracking>) pm.newQuery(q).execute(); 
    Map<Long, Long> mDeptLocationSelectedStatuses = new HashMap<Long, Long>();
    for (Tracking t : tracks) {
      if (t.getlocationId() != null) {
        if (!mDeptLocationSelectedStatuses.containsKey(t.getlocationId())) {
          mDeptLocationSelectedStatuses.put(t.getlocationId(), t.gettypeId());
        }
      }
    }
    System.out.println("Exit: mDeptLocationSelectedStatuses");
  }
  
  
  /**
   * Find the members currently at each of the primary dept's locations.
   * Result is a public list of all members.
   * @param list of pamIds
   * @return none
   */
  public void mDeptMembers(List<Long> pamDeptIds) {
    
    PersistenceManager pm = PMF.get().getPersistenceManager(); // TODO necessary? common?
    Query q = pm.newQuery(Member.class, ":p.contains(deptId)");
    q.setOrdering("deptId asc, id asc");    
    members = (List<Member>)q.execute(pamDeptIds);
    List<Member> tempDeptMembers = new ArrayList<Member>();
    for (Long d : pamDeptIds){
      for (Member m : members) {
        if (m.getdeptId().equals(d)){
          tempDeptMembers.add(m);
        }
      }
     mDeptMembers.put(d, tempDeptMembers);
    }
    System.out.println("Members qty inside deptMembers: " + members.size());    
    System.out.println("Exit: deptMembers");
  }
  
  
  /**
   * Find the members currently at each of the primary dept's locations.
   * Result is a privately available map of dept:list<Location>.
   * Note: mDeptActivityStatus map must be created prior to calling this method.
   * @param list of pamIds
   * @return none
   */
  public void mMemberLocation(Long alertId) {
    
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Query q = pm.newQuery(Tracking.class, "alertId == " + alertId + " && type == 'member' ");
    q.setOrdering("typeId asc, timeStamp desc");
    List<Tracking> tracks = (List<Tracking>) pm.newQuery(q).execute();
    System.out.println("tracks where member has a location: " + tracks.size());

    mMemberCurrentLocation.clear();
    for (final Tracking t : tracks) {
      if (!mMemberCurrentLocation.containsKey(t.gettypeId())) {
        mMemberCurrentLocation.put(t.gettypeId(), t);
      }
    }
    System.out.println("mMemberCurrentLocation qty: " + mMemberCurrentLocation.size());
    System.out.println("Exit: mMemberCurrentLocation");
  }
  
  
  /**
   * Find the members who have been manually added and not yet assigned a location
   * Result is a privately available list of unassigned members for the provided alert
   * Note: mDeptActivityStatus map must be created prior to calling this method.
   * @param list of pamIds
   * @return none
   */
  public void unassignedMembers() {
    
    unassignedMembers.clear();
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Iterator iter = mMemberCurrentLocation.entrySet().iterator();
    while (iter.hasNext()) {
      Entry<Long, Tracking> pairs = (Entry<Long, Tracking>)iter.next();
      if (pairs.getValue().getresponseType().equalsIgnoreCase("manuallyAddMember")) {
        Member m = pm.getObjectById(Member.class, pairs.getKey());
        unassignedMembers.add(m); 
        iter.remove();
      }
    }
    System.out.println("unassigned members qty: " + unassignedMembers.size());
    System.out.println("Exit: unassignedMembers");
    
  }

  
  /**
   * Find the members currently at each of the primary dept's locations.
   * Result is a privately available map of dept:list<Location>.
   * Note: mDeptActivityStatus map must be created prior to calling this method.
   * @param list of pamIds
   * @return none
   */
  public void mDeptLocationMembers() {
 
    PersistenceManager pm = PMF.get().getPersistenceManager();
    // dept iterator
    System.out.println("111 mDeptLocations inside mDeptLocationMembers: " + mDeptLocations.size());
    Iterator iterDept = mDeptLocations.entrySet().iterator();
    while (iterDept.hasNext()) {
      Entry<Long, List<Location>> pairsDept = (Entry<Long, List<Location>>)iterDept.next();
      if (!pairsDept.getValue().isEmpty()) {
        // location iterator
        for (Location l : pairsDept.getValue()) {
          // member iterator
          List<Member> mLocationMembers = new ArrayList<Member>(); 
          Iterator iterMember = mMemberCurrentLocation.entrySet().iterator();
          while (iterMember.hasNext()) {
            Entry<Long, Tracking> pairsMember = (Entry<Long, Tracking>)iterMember.next();        
            // determine members, regardless of dept, at this location
            if (l.getid().equals(pairsMember.getValue().getlocationId())) {
                Member m = pm.getObjectById(Member.class, pairsMember.getKey());
                mLocationMembers.add(m);
                System.out.println("m: " + m.getlastName());
                System.out.println("mLocationMembers qty: " + mLocationMembers.size());
            } 
          }
        mDeptLocationMembers.put(l.getid(), mLocationMembers); 
        }
      }
    }
  System.out.println("mDeptLocationMembers TOTAL qty: " + mDeptLocationMembers.size());
  System.out.println("Exit: mDeptLocationMembers");

  }

  
  
  private void locationTypes(Map<Long, List<Location>> mDeptLocations) {
    
    List<String> locationTypes = new ArrayList<String>();
    mDeptLocationTypes.clear();
    locationTypes = new ArrayList<String>(); 
    for (Entry<Long, List<Location>> mDeptLoc : mDeptLocations.entrySet()) {
      for (Location l : mDeptLoc.getValue()){
        if (!locationTypes.contains(l.gettype())) {
          locationTypes.add(l.gettype());
        }
      }
    mDeptLocationTypes.put(mDeptLoc.getKey(), locationTypes);
    }
  System.out.println("Exit: locationTypes");
  }
  
  
  // create List of Member Types
  private void memberTypes(Map<Long, List<Member>> mDeptMembers) {
    
    List<String> memberTypes = new ArrayList<String>();
    mDeptMemberTypes.clear();
    memberTypes = new ArrayList<String>(); 
    for (Entry<Long, List<Member>> entry : mDeptMembers.entrySet()) {
      for (Member m : entry.getValue()){
        if (!memberTypes.contains(m.gettype())) {
          memberTypes.add(m.gettype());
        }
      }
    mDeptMemberTypes.put(entry.getKey(), memberTypes);
    }
  System.out.println("Exit: memberTypes");
  }

  
  
  // determine which group each Member belongs to based on activity for this alert
  private void memberGroups(Map<Long, List<Member>> mDeptLocationMembers) {
    
    activeMembers.clear();
    //TODO replace with MultiMap
    Iterator iter = mDeptLocationMembers.entrySet().iterator();
    while (iter.hasNext()) {
      Entry<Long, List<Member>> pairs = (Entry<Long, List<Member>>)iter.next();
      if (pairs.getValue() != null) {
        activeMembers.addAll(pairs.getValue());
      }
    }
    List<Member> temp = new ArrayList<Member>();
    temp.addAll(members);
    memberGroups.put("active", activeMembers);
    System.out.println("active qty: " + activeMembers.size());
    temp.removeAll(activeMembers);
    inactiveMembers.clear();
    inactiveMembers.addAll(temp);
    System.out.println("inactive qty: " + inactiveMembers.size());
    memberGroups.put("inactive", inactiveMembers);
    alertMemberGroups.put(alertId, memberGroups);
    System.out.println("allMembers qty: " + allMembers.size());
    System.out.println("memberGroups qty: " + memberGroups.size());
    System.out.println("alertMemberGroups qty: " + alertMemberGroups.size());
    System.out.println("Exit: memberGroups");
  }
  
  
  // determine elapsed time between alert start and current system time
  private void elapsedTime(HttpServletResponse response, ServletRequest request, Alert a) {
    
    // format elapsed time
    SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
    SimpleDateFormat dateFormat2 = new SimpleDateFormat("HH:mm");
    dateFormat.setTimeZone(TimeZone.getTimeZone("EST"));
    dateFormat2.setTimeZone(TimeZone.getTimeZone("UTC"));
    Long alertTime = Long.valueOf(a.gettimeStamp());
    String alertElapsedTime = 
      dateFormat.format(new Date(System.currentTimeMillis() - alertTime - 19*3600000));
    
    // for display in the header of the report
    request.setAttribute("alertElapsedTime", alertElapsedTime);
    return;
  }
  
  
  private void alert(HttpServletResponse response, ServletRequest request) {

    // set alert cookie
    Long tempAlertId = Long.valueOf(request.getParameter("alertId"));
    if (tempAlertId != null) {
      Cookie c1 = new Cookie("cvAlertId", tempAlertId.toString());
      c1.setMaxAge(MAX_COOKIE_AGE_MS);
      response.addCookie(c1);
      alertId = tempAlertId;
    }
    // get alert object
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Alert a = pm.getObjectById(Alert.class, alertId);
    request.setAttribute("alert", a);
    elapsedTime(response, request, a);
  }
  

}

